package com.kexilo.logging.aspect;

import com.alibaba.fastjson2.JSON;
import com.kexilo.logging.domain.LogRecord;
import com.kexilo.logging.properties.LoggingProperties;
import com.kexilo.logging.service.LoggingService;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 日志切面 - 插件版本
 * 完全解耦，不依赖任何audit domain，只依赖LoggingService接口
 * 
 * @author Kexilo
 */
@Aspect
public class LoggingAspect {
    
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
    
    private final LoggingService loggingService;
    private final LoggingProperties properties;
    
    public LoggingAspect(LoggingService loggingService, LoggingProperties properties) {
        this.loggingService = loggingService;
        this.properties = properties;
        log.info("日志切面初始化完成，插件模式: {}", properties.getMode());
    }
    
    /**
     * 定义切点：拦截@Log注解
     */
    @Pointcut("@annotation(com.kexilo.core.common.annotation.Log)")
    public void logPointcut() {}
    
    /**
     * 前置通知：记录开始时间
     */
    @Before("logPointcut()")
    public void beforeMethod(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = 
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            request.setAttribute("startTime", System.currentTimeMillis());
        }
    }
    
    /**
     * 正常返回后处理
     */
    @AfterReturning(pointcut = "logPointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        handleLog(joinPoint, null, result);
    }
    
    /**
     * 异常抛出后处理
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception) {
        handleLog(joinPoint, exception, null);
    }
    
    /**
     * 处理日志记录
     */
    private void handleLog(JoinPoint joinPoint, Exception exception, Object result) {
        try {
            // 检查服务是否可用
            if (!loggingService.isAvailable()) {
                log.debug("日志服务不可用，跳过记录");
                return;
            }
            
            ServletRequestAttributes attributes = 
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return;
            }
            
            HttpServletRequest request = attributes.getRequest();
            
            // 检查是否应该排除此请求
            if (shouldExclude(request, joinPoint)) {
                return;
            }
            
            // 获取@Log注解
            Object logAnnotation = getLogAnnotation(joinPoint);
            if (logAnnotation == null) {
                return;
            }
            
            // 计算执行时间
            Long startTime = (Long) request.getAttribute("startTime");
            long costTime = startTime != null ? System.currentTimeMillis() - startTime : 0;
            
            // 构建日志记录
            LogRecord logRecord = buildLogRecord(joinPoint, request, logAnnotation, exception, result, costTime);
            
            // 记录日志
            loggingService.log(logRecord);
            
        } catch (Exception e) {
            // 日志记录失败不应影响业务流程
            log.error("日志切面处理失败", e);
        }
    }
    
    /**
     * 检查是否应该排除此请求
     */
    private boolean shouldExclude(HttpServletRequest request, JoinPoint joinPoint) {
        String uri = request.getRequestURI();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        
        // 检查排除的URL
        List<String> excludeUrls = properties.getExcludeUrls();
        for (String pattern : excludeUrls) {
            if (uri.matches(pattern.replace("*", ".*"))) {
                return true;
            }
        }
        
        // 检查排除的类
        List<String> excludeClasses = properties.getExcludeClasses();
        for (String pattern : excludeClasses) {
            if (className.matches(pattern.replace("*", ".*"))) {
                return true;
            }
        }
        
        // 检查排除的方法
        List<String> excludeMethods = properties.getExcludeMethods();
        for (String pattern : excludeMethods) {
            if (methodName.matches(pattern.replace("*", ".*"))) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 获取@Log注解
     */
    private Object getLogAnnotation(JoinPoint joinPoint) {
        try {
            // 通过反射获取方法上的@Log注解
            String methodName = joinPoint.getSignature().getName();
            Class<?> targetClass = joinPoint.getTarget().getClass();
            Method[] methods = targetClass.getMethods();
            
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    // 这里简化处理，实际应该精确匹配方法签名
                    return method.getAnnotations().length > 0 ? method.getAnnotations()[0] : null;
                }
            }
        } catch (Exception e) {
            log.debug("获取@Log注解失败", e);
        }
        return null;
    }
    
    /**
     * 构建日志记录
     */
    private LogRecord buildLogRecord(JoinPoint joinPoint, HttpServletRequest request, 
                                   Object logAnnotation, Exception exception, Object result, long costTime) {
        
        return LogRecord.builder()
                .type(LogRecord.LogType.OPERATION)
                .level(exception != null ? LogRecord.LogLevel.ERROR : LogRecord.LogLevel.INFO)
                .title(getLogTitle(logAnnotation))
                .username(getCurrentUsername())
                .ip(getClientIp(request))
                .userAgent(request.getHeader("User-Agent"))
                .url(request.getRequestURI())
                .method(request.getMethod())
                .className(joinPoint.getTarget().getClass().getName())
                .methodName(joinPoint.getSignature().getName())
                .params(getMethodParams(joinPoint, logAnnotation))
                .result(getMethodResult(result, logAnnotation))
                .status(exception != null ? 500 : 200)
                .errorMsg(exception != null ? exception.getMessage() : null)
                .costTime(costTime)
                .traceId(MDC.get("traceId"))
                .operTime(LocalDateTime.now())
                .tenantId(getCurrentTenantId())
                .operLocation(getOperLocation(request))
                .browser(getUserBrowser(request))
                .os(getUserOS(request))
                .build();
    }
    
    /**
     * 获取日志标题
     */
    private String getLogTitle(Object logAnnotation) {
        // 简化处理，实际应该通过反射获取@Log注解的title属性
        return "系统操作";
    }
    
    /**
     * 获取当前用户名
     */
    private String getCurrentUsername() {
        try {
            // 这里可以集成Spring Security或其他认证框架
            // 简化处理，返回默认值
            return "system";
        } catch (Exception e) {
            return "anonymous";
        }
    }
    
    /**
     * 获取客户端IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
    
    /**
     * 获取方法参数
     */
    private String getMethodParams(JoinPoint joinPoint, Object logAnnotation) {
        try {
            // 简化处理，可以根据@Log注解的配置决定是否记录参数
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length > 0) {
                return JSON.toJSONString(args);
            }
        } catch (Exception e) {
            log.debug("序列化方法参数失败", e);
        }
        return "";
    }
    
    /**
     * 获取方法返回值
     */
    private String getMethodResult(Object result, Object logAnnotation) {
        try {
            // 简化处理，可以根据@Log注解的配置决定是否记录返回值
            if (result != null) {
                return JSON.toJSONString(result);
            }
        } catch (Exception e) {
            log.debug("序列化方法返回值失败", e);
        }
        return "";
    }
    
    /**
     * 获取当前租户ID
     */
    private String getCurrentTenantId() {
        // 这里可以集成多租户框架
        return null;
    }
    
    /**
     * 获取操作地点
     */
    private String getOperLocation(HttpServletRequest request) {
        // 这里可以根据IP获取地理位置
        return "未知";
    }
    
    /**
     * 获取用户浏览器
     */
    private String getUserBrowser(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        if (StringUtils.hasText(userAgent)) {
            // 简化处理，实际可以用UserAgentUtils解析
            if (userAgent.contains("Chrome")) return "Chrome";
            if (userAgent.contains("Firefox")) return "Firefox";
            if (userAgent.contains("Safari")) return "Safari";
            if (userAgent.contains("Edge")) return "Edge";
        }
        return "Unknown";
    }
    
    /**
     * 获取用户操作系统
     */
    private String getUserOS(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        if (StringUtils.hasText(userAgent)) {
            // 简化处理，实际可以用UserAgentUtils解析
            if (userAgent.contains("Windows")) return "Windows";
            if (userAgent.contains("Mac")) return "macOS";
            if (userAgent.contains("Linux")) return "Linux";
            if (userAgent.contains("Android")) return "Android";
            if (userAgent.contains("iOS")) return "iOS";
        }
        return "Unknown";
    }
}
